package pl.edu.mimuw.irs.rcp.persistence.resource;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;

import org.eclipse.core.runtime.QualifiedName;

import pl.edu.mimuw.irs.core.model.design.resource.Resource;
import pl.edu.mimuw.irs.core.model.design.resource.Resources;
import pl.edu.mimuw.irs.core.model.design.resource.Role;
import pl.edu.mimuw.irs.core.model.design.resource.RoleInstance;
import pl.edu.mimuw.irs.core.util.Pair;
import pl.edu.mimuw.irs.rcp.persistence.descriptor.HistoryFilesDescriptor;
import pl.edu.mimuw.irs.rcp.persistence.resource.history.HistoryType;
import pl.edu.mimuw.irs.rcp.persistence.resource.history.ObjectFactory;
import pl.edu.mimuw.irs.rcp.persistence.resource.history.ResourceType;
import pl.edu.mimuw.irs.rcp.persistence.resource.history.RoleType;
import pl.edu.mimuw.irs.rcp.persistence.resource.history.TaskType;
import pl.edu.mimuw.irs.rcp.util.Logger;
import pl.edu.mimuw.irs.rcp.util.MessageCode;

public class HistoryLoader {

	public static final QualifiedName HISTORY_PATHS = 
		new QualifiedName("pl.edu.mimuw.irs.rcp.persistence.resource.history", "history_paths");
	
	// *** Attributes

	private List<File> files;
	private HistoryFilesDescriptor history;
	private Map<String, Map<String, Pair<Integer, Integer>>> timeMaps;
	public List<File> getFiles() {
		if (files == null) {
			if (history != null) {
				try {
					files = history.getFiles();
				} catch (Exception e) {
					Logger.handle(MessageCode.HISTORY_LOAD_FILE_PROBLEM, e);
				}
			}
			if (files == null) files = new LinkedList<File>();
		}
		return files;
	}
	
	// *** Constructors
	
	public HistoryLoader(HistoryFilesDescriptor history) throws Exception {
		this.history = history;
		this.files = history.getFiles();
	}
	
	// *** Methods
	
	public void loadTo(Resources resources) {
		Map<String, Resource> map = new LinkedHashMap<String, Resource>();
		for (Resource resource : resources) {
			map.put(resource.getName(), resource);
			Set<RoleInstance> toDelete = new LinkedHashSet<RoleInstance>();
			for (RoleInstance instance : resource.getRolesMap().values()) {
				if (!instance.isPreserve()) toDelete.add(instance);
				else {
					instance.setCalculatedModifier(null);
				}
			}
			for (RoleInstance instance : toDelete) {
				resource.remove(instance);
			}
		}
		
		if (timeMaps == null) {
			timeMaps = new LinkedHashMap<String, Map<String, Pair<Integer, Integer>>>();
			resetFiles();
			
			for (File file : getFiles()) {
				try {
					InputStream stream = new FileInputStream(file);
					loadFromStream(stream, timeMaps);
				} catch (Exception e) {
					Logger.handle(MessageCode.HISTORY_LOAD_FILE_MINOR_PROBLEM, e);
				}
			}
		}
		
		loadMaps(timeMaps, resources);
	}
	
	private void resetFiles() {
		this.files = null;
	}

	@SuppressWarnings("unchecked")
	protected void loadFromStream(InputStream stream, 
			Map<String, Map<String, Pair<Integer, Integer>>> timeMaps) throws Exception {
		JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		Object root = unmarshaller.unmarshal(stream);
		if (root == null) return;// TODO : neat exception
		if (!(root instanceof JAXBElement<?>)) return;
		JAXBElement<HistoryType> historyElement = (JAXBElement<HistoryType>) root;
		HistoryType historyType = historyElement.getValue();
		
		loadHistory(historyType, timeMaps);
	}

	private void loadHistory(HistoryType historyType, 
			Map<String, Map<String, Pair<Integer, Integer>>> timeMaps) {
		
		for (ResourceType resourceType : historyType.getResource()) {
			
			Map<String, Pair<Integer, Integer>> timeMap = timeMaps.get(resourceType.getName());
			if (timeMap == null) {
				timeMap = new LinkedHashMap<String, Pair<Integer, Integer>>();
				timeMaps.put(resourceType.getName(), timeMap);
			}
			for (TaskType taskType : resourceType.getTask()) {
				/* Pair(timeExpected, timeTaken) */
				for (RoleType roleType : taskType.getRole()) {
					Pair<Integer, Integer> pair;
					if (timeMap.containsKey(roleType.getName())) {
						pair = timeMap.get(roleType.getName());
						pair.setKey(pair.getKey() + taskType.getTimeExpected());
						pair.setValue(pair.getValue() + taskType.getTimeTaken());
					} else {
						pair = new Pair<Integer, Integer>(taskType.getTimeExpected(), taskType.getTimeTaken());
						timeMap.put(roleType.getName(), pair);
					}
				}
			}
		}
	}
	
	private void loadMaps(
			Map<String, Map<String, Pair<Integer, Integer>>> timeMaps,
			Resources resources) {
		for (Resource resource : resources) {
			Map<String, Pair<Integer, Integer>> timeMap = timeMaps.get(resource.getName());
			/* If no time map for this resource is found we proceed to the next one. */
			if (timeMap == null) continue;
			for (Entry<String, Pair<Integer, Integer>> entry : timeMap.entrySet()) {
				Double timeExpected = Double.valueOf(entry.getValue().getKey());
				Double timeTaken = Double.valueOf(entry.getValue().getValue());
				if (timeExpected <= 0 || timeTaken <= 0) continue;
				Role role = new Role(entry.getKey());
				RoleInstance roleInstance = resource.getRolesMap().get(role);
				Double calculatedModifier = timeTaken / timeExpected;
				if (roleInstance == null) {
					roleInstance = new RoleInstance(role, null, calculatedModifier);
					resource.add(roleInstance);
				} else {
					roleInstance.setCalculatedModifier(calculatedModifier);
				}
			}
		}
	}
	
	public void reset() {
		timeMaps = null;
	}
}
